package com.tn.tcp;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.gitee.hifong45.FE;
import com.gitee.hifong45.Kv;
import com.tn.controller.alarm.common.model.cardrobot.CardSenderRobotInfoDO;
import com.tn.dao.cardsender.CardSenderRobotDao;
import com.tn.service.cardrobot.impl.CardSendRobotServiceImpl;
import com.tn.service.external.subsystem.GateRestfulService;
import com.tn.socket.SocketProperties;
import com.tn.utils.JsonUtil;
import com.tn.websocket.WebSocket;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@Slf4j
public class CardMachineInData {

    public static HashMap<String, CardMachine> cardMachineMap = new HashMap<>(); // 机柜ID - 机柜对象

    @Value("${project.card.heart-beat-time:90}")
    private int heartBeatTime; //发卡机心跳包间隔

    @Value("${project.card.power-per-high: 80}")
    private int cardPowerPerHigh; // 集卡的电量百分比高位，可弹出

    public static int cardPopupTimtout; // 弹卡指令等待响应超时，
    @Value("${project.card.popup-time-out: 12}")
    public void setCardPopupTimeout(Integer value) {
        CardMachineInData.cardPopupTimtout = value;
    }

    @Autowired
    private CardSenderRobotDao cardSenderRobotDao;
    @Resource
    private SocketProperties socketProperties;
    @Resource
    private GateRestfulService gateRestfulService;

    @Resource
    private WebSocket webSocket;

    @Resource
    private CardSendRobotServiceImpl cardSendRobotServiceImpl;

    public static boolean showCMHeartBeatLog = false;
    public static boolean showCMAddOneCMLog = false;

    /**
     * 处理发卡机发来的报文信息
     * @param message
     * @return
     */
    public void doCardMachineMessage(ChannelHandlerContext ctx, String message){
        Map<String,String> map = CardMachineParamUtil.formatParam(message);
        if(MapUtil.isEmpty(map) || StrUtil.isBlank(map.get(CardMachineParamUtil.CMConsts.ACT))){
            log.error("收到数据解析错误: {}",message);
            NettyExt.sendMessage(ctx, CardMachineParamUtil.cmBackError(), true);
            return;
        }

        String backMsg = "";
        String act = map.get(CardMachineParamUtil.CMConsts.ACT);
        switch (act){
            //判断消息类型，并回传对应的报文
            case CardMachineParamUtil.CMConsts.CARD_MACHINE_ACT_LOGIN:
                backMsg = actLogin(map, ctx);
                break;
            case CardMachineParamUtil.CMConsts.CARD_MACHINE_ACT_SYNC_SETTING:
                backMsg = actSyncSetting(map, heartBeatTime);
                break;
            case CardMachineParamUtil.CMConsts.CARD_MACHINE_ACT_RETURN_BACK:
                backMsg = actReturnBack(map);
                break;
            case CardMachineParamUtil.CMConsts.CARD_MACHINE_ACT_HEART_BEAT:
                backMsg = actHeartbeat(map, ctx);
                break;
            case CardMachineParamUtil.CMConsts.CARD_MACHINE_ACT_POPUP_CONFIRM:
                backMsg = actPopupConfirm(map);
                break;
            default:
                backMsg = actDefult(map);
                break;
        }
        NettyExt.sendMessage(ctx, backMsg, true);
    }

    private String actDefult(Map<String, String>map) {
        return new StringBuilder("ERRCODE:0;ERRMSG:none;ACK:").append(map.get(CardMachineParamUtil.CMConsts.ACT)).toString();
    }

    // 发卡机“登录”
    private String actLogin(Map<String, String> map, ChannelHandlerContext ctx) {
        log.info("发卡机登录");
        String mac = map.get(CardMachineParamUtil.CMConsts.MAC);

        CardSenderRobotInfoDO cardSenderRobotInfoDO = cardSenderRobotDao.queryStationId(mac);
        if(null == cardSenderRobotInfoDO){
            log.info("mac{} 无匹配的发卡机信息", mac);
            return "none"; // 未找到匹配的
        }

        CardMachine cardMachine = BeanUtil.copyProperties(cardSenderRobotInfoDO, CardMachine.class);
        cardMachine.setIp(NettyExt.getClientIp(ctx));
        cardMachine.setType(cardSenderRobotInfoDO.getType());
        cardMachine.setCtx(ctx);
        this.putOne(cardMachine); // 加到列表缓存中
        log.info("mac{} 找到对应发卡机信息： {}", mac, cardMachine);

        String retStr = CardMachineParamUtil.cmBackLogin(cardMachine.getStationId());
        return retStr;
    }

    // 发卡机”请求配置“
    private String actSyncSetting(Map<String, String> map, int heartBeatTime) {
        String callback = CardMachineParamUtil.cmBackSyncSetting(socketProperties, heartBeatTime);
        return callback;
    }

    // 发卡机“心跳”
    private String actHeartbeat(Map<String, String> map, ChannelHandlerContext ctx) {
        String stationId = map.get(CardMachineParamUtil.CMConsts.STATIONID);
        if(StrUtil.isBlank(map.get(CardMachineParamUtil.CMConsts.CHKSUM))) {
            if(showCMHeartBeatLog) {
                log.error("心跳 解析失败: {}", map);
            }
            String callback = CardMachineParamUtil.cmBackHeartBeat();
            return callback;
        }

        CardMachine cardMachine = this.getByStationId(stationId);
        if(cardMachine == null) {
            cardMachine = new CardMachine();
            cardMachine.setStationId(stationId);
        }
        cardMachine.setUsableBattery(MapUtil.getInt(map, CardMachineParamUtil.CMConsts.USABLE_BATTERY));
        cardMachine.setEmptySlotCount(MapUtil.getInt(map, "EMPTY_SLOT_COUNT"));
        cardMachine.setTotal(MapUtil.getInt(map, "TOTAL"));
        cardMachine.setUsableBatteryNew(map.get("USABLE_BATTERY_NEW"));
        cardMachine.setCh(map.get("CH"));
        cardMachine.setUsableSlotCard(parseSlotCardMsg(map.get("CH")));

        cardMachine.setCtx(ctx); // tcp连接对象

        if(showCMHeartBeatLog) {
            log.info("心跳: {} - {}", cardMachine.getStationId(), cardMachine);
        }
        this.putOne(cardMachine); // 加到列表缓存中

        String callback = CardMachineParamUtil.cmBackHeartBeat();
        return callback;
    }

    // 发卡机回复“弹卡指令”。可以认为弹卡成功
    private String actPopupConfirm(Map<String, String> map) {
        String stationId = map.get("STATIONID"); // 响应的发卡机
        CardMachine cm = cardMachineMap.get(stationId);
        if(cm == null || StrUtil.isBlank(cm.getPopupSlot()) || SystemClock.now() - cm.getPopupBeginTime() > 60 * 1000) {
            cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_FAIL);
        } else {
            var wantPopupSlot = cm.getPopupSlot(); // 准备弹的
            var slot = map.get("SLOT"); // 此次消息中说明的
            if(StrUtil.equalsIgnoreCase(wantPopupSlot, slot)) {
                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_SUCCESS); // 成功
                var slotCards = cm.getUsableSlotCard();
                if(slotCards.containsKey(slot)) {
                    slotCards.remove(slot);
                    var endTime = SystemClock.now();
                    log.info("  弹卡成功 {} - {}，耗时： {}ms, 剩余 {}个： {} ", stationId, slot, endTime - cm.getPopupBeginTime(), slotCards.size(), slotCards.keySet());
                }
            } else {
//                log.error("{} 想弹出 {}，实际弹出 {}", stationId, wantPopupSlot, slot);
//                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_FAIL);
            }

        }
        String stationId1=null;
        String usable=null;
        Kv carMachineInfo = cardSendRobotServiceImpl.getCarMachineInfo(stationId1, StrUtil.isBlank(usable));
        webSocket.sendOneMessage("hs02",JsonUtil.obj2String(carMachineInfo));
        String callback = CardMachineParamUtil.cmBackPopupConfirm("");
        return callback;
    }

    // 发卡机上报“还卡”
    private String actReturnBack(Map<String, String> map) {
        String stationId = map.get(CardMachineParamUtil.CMConsts.STATIONID); // 发卡机
        String cardId = map.get(CardMachineParamUtil.CMConsts.ID); // 集卡
        if(StrUtil.isNotBlank(stationId) && StrUtil.isNotBlank(cardId)){
            log.info("发卡机还卡： {} - {} - {}", stationId, cardId,JsonUtil.obj2String(map));
            gateRestfulService.removeBinding(cardId); //解绑标签
            // 集卡加入该发卡机（暂不符，协议中没有 槽位）
            String stationId1=null;
            String usable=null;
            Kv carMachineInfo = cardSendRobotServiceImpl.getCarMachineInfo(stationId1, StrUtil.isBlank(usable));
            webSocket.sendOneMessage("hs02", JsonUtil.obj2String(carMachineInfo));

            CardMachine cardMachine = this.getByStationId(stationId);
            if(cardMachine == null) {
                cardMachine = new CardMachine();
                cardMachine.setStationId(stationId);
            }
            cardMachine.setUsableBattery(MapUtil.getInt(map, CardMachineParamUtil.CMConsts.USABLE_BATTERY));
            cardMachine.setEmptySlotCount(MapUtil.getInt(map, "EMPTY_SLOT_COUNT"));
            cardMachine.setTotal(MapUtil.getInt(map, "TOTAL"));
            cardMachine.setUsableBatteryNew(map.get("USABLE_BATTERY_NEW"));
            cardMachine.setCh(map.get("CH"));
            cardMachine.setUsableSlotCard(parseSlotCardMsg(map.get("CH")));
            this.putOne(cardMachine); // 加到列表缓存中
        }
        String callback = CardMachineParamUtil.cmBackReturnBack(cardId);
        return callback;
    }

    // 解析报文中“卡槽-卡”关系
    private Map<String, SlotCard> parseSlotCardMsg(String msg) {
//        List<SlotCard> slotCardList = new ArrayList<>();
        Map<String, SlotCard> slotCardMap = new HashMap<>();
        //S1,1,100,6066050073|S2,1,100,6021120059|S3,1,100,5705009574|S4,0,0,0000000000|S5,0,0,0000000000|S6,0,0,0000000000|S7,0,0,0000000000|S8,0,0,0000000000|S9,0,0,0000000000|
        List<String> lst = StrUtil.split(msg, "|");
        for(String slotStr : lst) {
            if(StrUtil.isNotBlank(slotStr)) {
                List<String> slotLst = StrUtil.split(slotStr, ",");
//                [S1,1,100,6066050073], [S4,0,0,0000000000]
                if(slotLst.size() != 4) {
                    log.error("格式不对，继续: {}", slotStr);
                    continue; // 格式不对，继续
                }
                String slot = slotLst.get(0).substring(1); // 槽位
                if(slotLst.get(1).equals("1") && Integer.valueOf(slotLst.get(2)) >= cardPowerPerHigh) {
                    SlotCard slotCard = new SlotCard();
                    slotCard.setCardId(slotLst.get(3)); // 卡号
                    slotCard.setPowerper(Integer.valueOf(slotLst.get(2))); // 电量百分比
                    slotCard.setSlot(slot);

                    slotCardMap.put(slotCard.getSlot(), slotCard); // 加入: 槽位 - 槽卡实例
                } else {
                    slotCardMap.remove(slot); // 该槽位无卡，或者电量不足，则移除
                }
            }
        }
        return slotCardMap;
    }


    public CardMachine getByStationId(String stationId) {
        return this.cardMachineMap.get(stationId);
    }

    // 将一个发卡机实例添加到对象表中
    public void putOne(CardMachine cardMachine) {
        if(cardMachine == null || StrUtil.isBlank(cardMachine.getStationId())) {
            log.error("cardMachine 或 stationId = null");
            return; // 错误
        }
        String stationId = cardMachine.getStationId(); // 发卡机号
        CardMachine cm = CardMachineInData.cardMachineMap.get(stationId);
        if(cm != null) {
            // 该发卡机已有对象，移除
//            ChannelHandlerContext ctx = cm.getCtx();
//            log.info("已有，移除相同的，CMChannels剩余 {}", CardMachineHandle.removeChannel(ctx));
        }
        this.cardMachineMap.put(cardMachine.getStationId(), cardMachine);
        if(showCMAddOneCMLog) {
            log.info("cardMachineMap有 {}个: {}", cardMachineMap.size(), cardMachineMap.keySet());
        }
    }


    // 弹出发卡机某槽位或指定槽位的集卡. 返回卡号
    public static String popCardGetCardId(CardMachine cm, String... argSlot) {
        String slot = "";
        String cardId = "";
        // 弹出指定槽位的集卡，只执行一次弹卡动作
        if(argSlot.length == 1 && StrUtil.isNotBlank(argSlot[0])) {
            slot = argSlot[0]; // 槽位
            SlotCard sc = cm.getUsableSlotCard().get(slot);
            FE.nullThrow(sc, StrUtil.format("该发卡机{} 无此槽位{}，或此槽位无卡", cm.getStationId(), slot));

            cardId = cm.getUsableSlotCard().get(slot).getCardId(); // 成功才能返回卡号
            if(!sendPopCardAndWait(cm, slot)) {
                cardId = ""; // 成功才能返回卡号
            }
        } else {
            // 不指定槽位，则顺序尝试，直到成功弹出一个，或全都失败
            for(Map.Entry<String, SlotCard> entry : cm.getUsableSlotCard().entrySet()) {
                if(entry.getValue().getPowerper()<20){
                    continue;
                }
                slot = entry.getKey(); // 槽位
                cardId = entry.getValue().getCardId(); // 弹卡成功
                // 如果弹卡结果失败，则继续弹下一个
                if(sendPopCardAndWait(cm, slot)) {
                    break; // 弹卡成功，结束
                }
                cardId = ""; // 成功才能返回卡ID
            }
        }
        return cardId;
    }


    // 后台弹出发卡机某槽位或指定槽位的集卡. 返回卡号
    public static String popCardGetCardId1(CardMachine cm, String... argSlot) {
        String slot = "";
        String cardId = "";
        // 弹出指定槽位的集卡，只执行一次弹卡动作
        if(argSlot.length == 1 && StrUtil.isNotBlank(argSlot[0])) {
            slot = argSlot[0]; // 槽位
            SlotCard sc = cm.getUsableSlotCard().get(slot);
            FE.nullThrow(sc, StrUtil.format("该发卡机{} 无此槽位{}，或此槽位无卡", cm.getStationId(), slot));

            cardId = cm.getUsableSlotCard().get(slot).getCardId(); // 成功才能返回卡号
            if(!sendPopCardAndWait(cm, slot)) {
                cardId = ""; // 成功才能返回卡号
            }
        } else {
            // 不指定槽位，则顺序尝试，直到成功弹出一个，或全都失败
            for(Map.Entry<String, SlotCard> entry : cm.getUsableSlotCard().entrySet()) {
                slot = entry.getKey(); // 槽位
                cardId = entry.getValue().getCardId(); // 弹卡成功
                // 如果弹卡结果失败，则继续弹下一个
                if(sendPopCardAndWait(cm, slot)) {
                    break; // 弹卡成功，结束
                }
                cardId = ""; // 成功才能返回卡ID
            }
        }
        return cardId;
    }


    // 发送弹卡指令，并等待发卡机的返回结果
    public static boolean sendPopCardAndWait(CardMachine cm, String slot) {
        cm.setPopupSlot(slot); // 等待弹出
        cm.setPopupBeginTime(SystemClock.now()); // 开始计时
        cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_BEGIN); // 开始弹出

        log.info("  准备弹 {}-{} 的卡", cm.getStationId(), slot);
        String msg = CardMachineParamUtil.cmCmdPopupCard(slot); // 弹出指定卡槽的集卡
        NettyExt.sendMessage(cm.getCtx(), msg, true); // 发送弹卡指令

        // 每次等待xx毫秒，循环几次，算超时
        for(int loop=0; loop<cardPopupTimtout * 2; loop++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(cm.getPopupStatus() == CardMachineParamUtil.CMConsts.POPUP_CARD_SUCCESS) {
                cm.getUsableSlotCard().remove(slot); // 移除已弹出卡对应的槽位-集卡对象
                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_NULL);
                log.info("  弹卡成功");
                return true;
            } else if(cm.getPopupStatus() == CardMachineParamUtil.CMConsts.POPUP_CARD_FAIL) {
                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_NULL);
                log.info("  弹卡失败");
                return false;
            }
        }
        return false; // 超过500毫秒
    }

    // 弹出，不清除，需在上层清除
    public static String popCardGetCardIdNoRemove(CardMachine cm, String slot) {
        SlotCard sc = cm.getUsableSlotCard().get(slot);
        if(sc == null) {
            log.error("该发卡机的卡槽位信息不存在，或此槽位无卡： {} - {}", cm.getStationId(), slot);
            cm.getUsableSlotCard().remove(slot);
            return "";
        }

        String cardId = cm.getUsableSlotCard().get(slot).getCardId(); // 成功才能返回卡号

        cm.setPopupSlot(slot); // 等待弹出
        cm.setPopupBeginTime(SystemClock.now()); // 开始计时
        cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_BEGIN); // 开始弹出

        log.info("  准备弹卡 {} - {}，等待 {}秒", cm.getStationId(), slot, cardPopupTimtout);
        String msg = CardMachineParamUtil.cmCmdPopupCard(slot); // 弹出指定卡槽的集卡
        NettyExt.sendMessage(cm.getCtx(), msg, true); // 发送弹卡指令

        // 每次等待xx毫秒，循环几次，算超时
        for(int loop=0; loop<cardPopupTimtout * 2; loop++) {
            ThreadUtil.sleep(500);
            if(cm.getPopupStatus() == CardMachineParamUtil.CMConsts.POPUP_CARD_SUCCESS) {
                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_NULL);
                cm.setPopupBeginTime(0L);
                cm.setPopupSlot("");
                return cardId; // 成功
            } else if(cm.getPopupStatus() == CardMachineParamUtil.CMConsts.POPUP_CARD_FAIL) {
                cm.setPopupStatus(CardMachineParamUtil.CMConsts.POPUP_CARD_NULL);
                return "";
            }
        }
        cm.setPopupSlot("");

        return "";
    }

}
